
/**
 * 内存泄漏（memory leak）
 * 
 * 什么是内存泄漏？
 * 
 * 程序的运行需要内存。只要程序提出要求，操作系统或者运行时（runtime）就必须供给内存。
 * 对于持续运行的服务进程（daemon），必须及时释放不再用到的内存。
 * 否则，内存占用越来越高，轻则影响系统性能，重则导致进程崩溃。
 * 
 * 
 * 不再用到的内存，没有及时释放(返还给操作系统或空闲内存池)，就叫做内存泄漏
 * 
 */


// 几种常见的内存泄漏

// 一、 意外的全局变量
// 正常逻辑来说，如果变量放到全局那意味着这变量随时会被使用到，只要JS运行，就会一直占着内存

// 但如果是意外的、不常使用到的变量，也会一直占着内存，造成内存泄漏

// 全局变量的生命周期，逻辑上来说，要等浏览器关闭掉，才会销毁

// function foo(arg) {
//   bar = "this is a hidden global variable";
// }

// function foo() {
//   this.bar = "potential accidental global";
// } 
// foo();

// 实际上变量会定义在全局 window.bar

// 解决： window.bar = null; delete window.bar


// 二、 对DOM的引用
// 以下方式，虽然都删除了dom本身，但其保存的引用地址还存在内存中，会造成内存泄漏

// 1. dom有两个引用：存在定义的对象里、存在DOM树中
// var elements = {  
//   button: document.getElementById("button"),  
//   image: document.getElementById("image"),  
//   text: document.getElementById("text")  
// };  
// function doStuff(){  
//   elements.button.click();
// }  
// function removeButton(){  
//   document.body.removeChild(document.getElementById('button'));
// }

// 2. 给dom节点定义事件后删除dom节点，事件本身也是引用值，一直存在内存中
// const oBtn = document.querySelector('#button');
// oBtn.addEventListener('click', handlerBtnClick, false);
// function handlerBtnClick() {};
// oBtn.remove();

// 解决： handlerBtnClick = null


// 三、被遗漏的定时器和回调函数
// 定时器一直存在。 同时回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放

// var someResouce = getData();  
// setInterval(function() {  
//   var node = document.getElementById('Node');  
//   if(node){  
//     node.innerHTML = JSON.stringify(someResouce)
//   }
// }, 1000)


// 四、闭包

// function test() { // 普通函数
//   var a = 0;
//   console.log(a);
// }
// 当函数执行完，ao(ao存有变量 ->作用域) 就不存在了， 

// function test1() {
//   var a = 1;
//   function test2() {
//     a++;
//     console.log(a);
//   }
//   test2();
// }
// test1(); // 2
// test1(); // 2
// test1(); // 2

// 每次执行完函数，内部变量就会销毁

// function test1() { // 闭包函数
//   var a = 1;
//   function test2() {
//     a++;
//     console.log(a);
//   }
//   return test2;
// }
// var test = test1();
// test(); // 2
// test(); // 3
// test(); // 4
// test = null;
// test();

// test1 被定义时，系统生成作用域链
// test1() 返回的是 test2 的作用域 ——> 保存的是test1的作用域链

// 按理说， test1实现完毕后，AO就会释放掉，但返回的test2的作用域链也是包含了这个(test1)AO的指向
// 意味着 test1 的指向断了，但存储在内存中的地址还在
// 这个 test2 被保存到了全局(test)，当 test 执行的时候，test2 的作用域链上增加AO。
// 当打印a时，在自己的AO上没有找到，就会向test1 的AO 找。所以test1 的AO，(变量a的地址) 一直存在内存中;


// 内存泄漏，想解决，实际上就是解除在内存中的引用！

/**
 * 垃圾回收机制 (garbage collector)
 * 
 * 1. 找出不再使用的变量
 * 2. 释放其占用的内存
 * 3. 固定的时间间隔 运行
 * 
 * 垃圾回收机制怎么知道，哪些内存不再需要呢，
 * 实际上就是给变量打上标记、记号，用来识别是否还有用、是否还有存在的价值，是否需要回收
 * 
 * 两种方式
 * 1. 标记清除 (mark and sweep)
 * 2. 引用计数
 */

// 标记清除
// 几乎所有的浏览器都在使用，简单，明了，没有风险(IE6不行)

function test() {
  var a = 0; // 打上标记：进入环境
}
test(); // 打上标记：离开环境
var b = 2;
var c = 3;
var e = function() {}

// 引用计数 reference counting
// 语言引擎有一张"引用表"，保存了内存里面所有的资源（通常是各种值）的引用次数。如果一个值的引用次数是0，就表示这个值不再用到了，因此可以将这块内存释放
// 记录每个值被引用的次数(低版本的IE容易发生内存泄漏)

function t() {
  var a = new Object(); // a = 1
  var b = new Object(); // b = 1
}

// var c = a; // a++ = 2
// var c = b; // a-- = 1
// 但
a.prop = b; // b = 2
b.prop = a; // a = 2
// 引用一直销毁不了, 不会为0

// 解决（解除引用）
a = null;
b = null;

// 垃圾回收机制在周期(时间间隔)执行中
// 首先会排除环境(全局)变量、(闭包)所形成的AO变量


// 怎样避免内存泄漏
// 1、减少不必要的全局变量，或者生命周期较长的对象，及时对无用的数据进行垃圾回收；

// 2、注意程序逻辑，避免“死循环”之类的 ；

// 3、避免创建过多的对象  原则：不用了的东西要及时归还。
